package io.gitee.tziye.jdbc;

import com.zaxxer.hikari.HikariDataSource;
import io.gitee.tziye.common.RenaiUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.SimpleJdbcCall;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

@Slf4j
public class JdbcProxy {

    public final JdbcTemplate jdbcTemplate;
    public final TransactionTemplate transactionTemplate;
    public final NamedParameterJdbcTemplate namedJdbcTemplate;
    public final String SCHEMA;

    public JdbcProxy(HikariDataSource dataSource) {
        SCHEMA = dataSource.getSchema();
        namedJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
        jdbcTemplate = namedJdbcTemplate.getJdbcTemplate();
        PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
        transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args) throws DataAccessException {
        return doRetry(() -> jdbcTemplate.query(sql, rowMapper, args));
    }

    public <T> List<T> queryForList(String sql, Class<T> elementType, Object... args) throws DataAccessException {
        return doRetry(() -> jdbcTemplate.queryForList(sql, elementType, args));
    }

    public <T> T queryForObject(String sql, Class<T> requiredType, Object... args) throws DataAccessException {
        return doRetry(() -> jdbcTemplate.queryForObject(sql, requiredType, args));
    }

    public int update(String sql, Object... args) throws DataAccessException {
        return doRetry(() -> jdbcTemplate.update(sql, args));
    }

    public int[] batchUpdate(String sql, List<Object[]> batchArgs) throws DataAccessException {
        return doRetry(() -> jdbcTemplate.batchUpdate(sql, batchArgs));
    }

    public <T> List<T> namedQuery(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper) throws DataAccessException {
        return doRetry(() -> namedJdbcTemplate.query(sql, paramMap, rowMapper));
    }

    public int namedUpdate(String sql, Map<String, ?> paramMap) throws DataAccessException {
        return doRetry(() -> namedJdbcTemplate.update(sql, paramMap));
    }

    public ProcedureResult call(String name, Map<String, Object> params) {
        return doRetry(() -> {
            SimpleJdbcCall call = new SimpleJdbcCall(jdbcTemplate);
            Map<String, Object> result = call.withCatalogName(SCHEMA)
                    .withProcedureName(name).execute(params);
            return new ProcedureResult(result);
        });
    }

    public <T> T doInTransaction(Supplier<T> supplier) {
        return transactionTemplate.execute(action -> supplier.get());
    }

    private <T> T doRetry(Supplier<T> supplier) {
        int i = 0;
        while (true) {
            try {
                return supplier.get();
            } catch (Exception e) {
                log.debug("Lock conflict, try again", e);
                if (i < 50) {
                    RenaiUtil.sleepMS(100);
                    i++;
                } else {
                    throw e;
                }
            }
        }
    }
}